package com.autonavi.yunda.yunji.controller;

import com.autonavi.yunda.yunji.common.exception.AmapException;
import com.autonavi.yunda.yunji.common.exception.AmapExceptionCode;
import com.autonavi.yunda.yunji.common.exception.AmapPreconditions;
import com.autonavi.yunda.yunji.controller.request.DatasourceConfigRequest;
import com.autonavi.yunda.yunji.core.enums.DBType;
import com.autonavi.yunda.yunji.core.enums.DatasourceType;
import com.autonavi.yunda.yunji.core.enums.OperationRecordType;
import com.autonavi.yunda.yunji.core.model.condition.DatasourceQueryCondition;
import com.autonavi.yunda.yunji.core.model.dto.DatasourceConfigDTO;
import com.autonavi.yunda.yunji.core.model.dto.DatasourceProjectMappingDTO;
import com.autonavi.yunda.yunji.core.service.DatasourceConfigService;
import com.autonavi.yunda.yunji.core.service.DatasourceProjectMappingService;
import com.autonavi.yunda.yunji.core.service.dbfactory.DbFactoryService;
import com.autonavi.yunda.yunji.core.service.dbfactory.OssFactoryService;
import com.autonavi.yunda.yunji.core.vo.datasource.DbTableInfo;
import com.autonavi.yunda.yunji.core.vo.response.DataSourcePageVO;
import com.autonavi.yunda.yunji.service.DataSourceInfoService;
import com.autonavi.yunda.yunji.service.OperationRecordService;
import com.autonavi.yunda.yunji.service.ProjectAuthService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@RestController
@RequestMapping("/dataSource")
public class DatasourceConfigController {


    @Resource
    DataSourceInfoService dataSourceInfoService;
    @Resource
    OssFactoryService ossFactoryService;
    @Resource
    DatasourceConfigService datasourceConfigService;
    @Resource
    private OperationRecordService operationRecordService;

    @Resource
    DatasourceProjectMappingService datasourceProjectMappingService;

    @Resource
    private ProjectAuthService projectAuthService;


    private static final ObjectMapper mapper = new ObjectMapper();
    @Autowired
    private DbFactoryService dbFactoryService;

    @Transactional(rollbackFor = Exception.class)
    @PostMapping("add")
    public Long add(@Validated @RequestBody DatasourceConfigRequest request) {
        //check project permission for current user
        this.projectAuthService.checkPermissionAny(request.getProjectIds());
        datasourceConfigService.checkDbNameOrThrow(request.getDbname());
        Long id = datasourceConfigService.add(request.toAddDTO());
        datasourceProjectMappingService.updateMapping(id, request.getProjectIds(), request.getOperator());
        operationRecordService.add(OperationRecordType.DatabaseSubType.CREATE, id.toString());
        return id;
    }

    @Transactional(rollbackFor = Exception.class)
    @PostMapping("update")
    public void update(@RequestBody DatasourceConfigRequest request) {
        Long id = request.getId();
        AmapPreconditions.checkArgumentForUser(id != null, "id 不能为空");
        if (StringUtils.isNotBlank(request.getDbname())) {
            datasourceConfigService.checkDbNameOrThrow(request.getDbname());
        }
        DatasourceConfigDTO dto = datasourceConfigService.findByIdOrThrow(id);
        datasourceConfigService.update(request.toUpdateDTO(dto));
        datasourceProjectMappingService.updateMapping(id, request.getProjectIds(), request.getOperator());
        operationRecordService.add(OperationRecordType.DatabaseSubType.UPDATE, request.getId().toString());
    }

    @Transactional(rollbackFor = Exception.class)
    @GetMapping("delete")
    public void delete(Long id) {
        AmapPreconditions.checkArgumentForUser(id != null, "id 不能为空");
        DatasourceConfigDTO dto = datasourceConfigService.findByIdOrThrow(id);
        dto.setIsDeleted("1");
        datasourceConfigService.update(dto);
        datasourceProjectMappingService.deleteByDatasourceId(id);
        operationRecordService.add(OperationRecordType.DatabaseSubType.DELETE, id.toString());
    }

    @PostMapping("list")
    public DataSourcePageVO listByCondition(@Validated @RequestBody DatasourceQueryCondition condition) {
        List<String> projectIds = projectAuthService.listAuthorizedProjectIds();
        Map<Long, Set<String>> dbPidMap
                = datasourceProjectMappingService.listByProjectIds(projectIds)
                .stream().collect(
                        Collectors.groupingBy(DatasourceProjectMappingDTO::getDatasourceId,
                                Collectors.mapping(DatasourceProjectMappingDTO::getProjectId, Collectors.toSet())));
        condition.ids = dbPidMap.keySet();
        condition.setTypeList(Arrays.stream(DatasourceType.values()).collect(Collectors.toList()));
        DataSourcePageVO dataSourcePageVO = datasourceConfigService.listByCondition(condition);
        dataSourcePageVO.getDataSourceConfigList().forEach(dataSourceVO -> dataSourceVO.setProjectIds(dbPidMap.get(dataSourceVO.getId())));
        return dataSourcePageVO;
    }

    /**
     * 获取数据源列表
     *
     * @param dbType 资源类型
     */
    @GetMapping("getDBList")
    public List<Map<String, String>> getDbList(@RequestParam(required = false, defaultValue = "SQL") DBType dbType, @RequestParam(required = false) String projectId) {
        List<String> dbIds = null;
        if (StringUtils.isNotBlank(projectId)) {
            dbIds = datasourceProjectMappingService.listDatasourceIdsByPid(projectId);
            if (dbIds.isEmpty()) {
                return new ArrayList<>();
            }
        }
        return datasourceConfigService.selectList(dbType.getDatasourceTypes(), dbIds).stream()
                .map(dto -> Map.of("dbName", dto.getDbname(), "desc", dto.getDescription()))
                .collect(Collectors.toList());
    }

    /**
     * 获取数据库表描述
     */
    @GetMapping("getTableList")
    public List<DbTableInfo> getTableList(@RequestParam String dbName) {
        try {
            return dataSourceInfoService.getAllTableInfo(dbName);
        } catch (Exception e) {
            throw new AmapException(AmapExceptionCode.COMMON_SERVER_WARN, "无权限获取表结构数据，请尝试直接写sql进行查询", e);
        }
    }

    @GetMapping("getSingleTableList")
    public List<DbTableInfo> getSingleTableList(@RequestParam String dbName, @RequestParam String tableName) {
        return dataSourceInfoService.getSingleTableInfoList(dbName, tableName);
    }

    @GetMapping("getTable")
    public DbTableInfo getTable(@RequestParam String dbName, @RequestParam String tableName) {
        return dataSourceInfoService.getTableInfo(dbName, tableName);
    }

    /**
     * 获取oss的bucket列表
     */
    @GetMapping("getOssBucketList")
    public List<String> getOssBucketList(@RequestParam String endpoint, @RequestParam String accessKeyId, @RequestParam String accessKeySecret) {
        return ossFactoryService.getOssBucketList(endpoint, accessKeyId, accessKeySecret);
    }

    /**
     * 代码生成时获取数据详情（外部代码生成器使用）
     */
    @GetMapping("connect/detail")
    public Map<String, Object> dataSourceConnectDetail(@RequestParam String dbName) throws JsonProcessingException {
        DatasourceConfigDTO dto = datasourceConfigService.selectOneByDbName(dbName);
        Map<String, Object> dsConfig = new HashMap<>(8);
        dsConfig.put("type", dto.getType().name());
        dsConfig.put("config", mapper.readValue(dto.getConfig(), Map.class));
        return dsConfig;
    }

    /**
     * 编排前端用于获取某种数据库列表
     */
    @GetMapping("type/list")
    public List<Map<String, String>> getDbListByType(@RequestParam String type, @RequestParam(required = false) String projectId) {
        switch (type) {
            case "MYSQL":
            case "TDDL":
                return getDbList(DBType.SQL, projectId);
            case "OSS":
                return getDbList(DBType.OSS, projectId);
            case "REDIS":
                return getDbList(DBType.REDIS, projectId);
            default:
        }
        return Collections.emptyList();
    }

    @GetMapping("removeDb")
    public void removeDb(Long id) {
        AmapPreconditions.checkArgumentForUser(id != null, "id 不能为空");
        DatasourceConfigDTO dto = datasourceConfigService.findByIdOrThrow(id);
        dto.getType().getFactoryBean().removeDataSource(dto.getDbname());
    }

    @GetMapping("testConnection")
    public void testConnection(Long id) {
        AmapPreconditions.checkArgumentForUser(id != null, "id 不能为空");
        DatasourceConfigDTO dto = datasourceConfigService.findByIdOrThrow(id);
        dto.getType().getFactoryBean().testConnection(dto.getDbname());
    }
}
